#usr/bin/env python
#conding:utf-8
#卫星定位设备计算存库模块
# 配合数据库表格字段更新进行计算和存储模块的更新
# 1、redis的数据，整体获取和计算
# 2、对dynamic字段的数据，进行整体更新，即设备上传的最新数据，全部update到该字段
# 3、整体计算各种报警数据
# 4、批量更新，批量存储
# 吕康宁，2020.06.18
from sqlalchemy import Column,String,Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import desc
from sqlalchemy.ext.automap import automap_base	
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
import datetime
import json
import math
import time
import redis
from compute.data_compute import *
import numpy as np
import pandas as pd
from collections import defaultdict
import copy

#导入数据库映射，表示用sqlachemy操作django的model创建的数据库
#连接django在使用的postgresql数据库
Base = automap_base()
engine = create_engine('sqlite:///../car.db')
Base.prepare(engine, reflect=True)	



#建立数据库映射
Device=Base.classes.car_app_device
Multi_Media=Base.classes.car_app_multi_media
Alarm=Base.classes.car_app_alarm
Command_Log=Base.classes.car_app_command_log
session = Session(engine)
#连接REDIS
pool_redis= redis.ConnectionPool(host='localhost',port=6379,decode_responses=True)
mesg_redis=redis.Redis(connection_pool=pool_redis)


mesg_to_db_field={'lbs': '0', 'mileage': '0', 'speed': '0', 'othermesg':{}, 'wifi': '0', 
				  'oil_use': '0', 'dirct': '0', 'scanner': '0', 'rssi': '0', 'efence_alarm': '0', 
				  'status': '0', 'remark': '0', 'heart_rate': '0', 'speed_alarm': '0', 'battery': '0', 
				  'blood_pres': '0', 'stop_time': '0','sos_alarm': '0', 'step_count': '0', 
				  'heart_alarm': '0', 'device_id':'0', 'dev_upload': '0', 'obd': '0', 'blood_alarm': '0', 
				  'lng': '0', 'serv_receive': '0', 'track_type': '0', 'location': '0', 'lat': '0', 'hard_verson': '0'}

def log_write(data):#日志log存储
	with open('device_log','a') as f:
		f.write(data)
		f.write('\n')


print('start---',datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
while True:
	#从redis批量取数据，
	# print('start---')
	update_data=mesg_redis.lrange('car_gps_data',0,-1)
	# print('update_data==>',len(update_data),update_data)
	# 销毁已经获取的数据,获取数据和销毁数据之间，可能有新数据加入了列表，所有销毁的数据的范围就是获取的数据的长度到最后一位
	mesg_redis.ltrim('car_gps_data',len(update_data),-1)

	if update_data==[]:
		# print('no data')
		time.sleep(1)
	else:
		#通过redis作为信息通道，批量加载device设备信息到redis，先从redis里get，如果get不到，pandas取数据库数据，加载到redis
		#1、作为判断设备是否录入，是否需要进行计算处理的依据
		#2、获取，计算围栏，超速，等报警所需要的设置参数
		start=''
		end=''
		this_dynamic={}
		save_data_list=[]
		comd_up_list=[]
		alarm_up_list=[]

		# 计算路线，需要加载路线信息
		polyline={}
		sql_polyline='select * from car_app_efence where kind=\'polyline\''
		line=pd.read_sql_query(sql_polyline,engine)
		line.apply(lambda x:polyline.update({x['name']:x.to_dict()}),axis=1)
		# print(polyline)

		# 加载未处理的报警信息，用于报警更新
		alarm_unhandle={}
		sql_alarm='select * from car_app_alarm where handle=\'no\''
		alarm_detail=pd.read_sql_query(sql_alarm,engine)
		alarm_detail.apply(lambda x:alarm_unhandle.update({x['name']:x.to_dict()}),axis=1)
		
		# 从数据库加载device数据，获取所有device的id。
		sql_device = 'select * from car_app_device'
		device = pd.read_sql_query(sql_device,engine)
		exist_dev_id=device['device_id'].tolist()

		# 整理新接收的数据为字典，如果是已录入的设备，进行打包处理，添加到save_data_list。
		update_data=pd.DataFrame(update_data)
		update_data[0]=update_data[0].map(lambda x:eval(x))

		def save_to_db(raw_data,Base):
			# print('cuca_num====>')
			# print('raw_data====>',raw_data)
			if not Base.classes.has_key(raw_data['device_id']):
				Base = automap_base()
				engine = create_engine('sqlite:///car.db')
				Base.prepare(engine, reflect=True)	

			obj_to_db=Base.classes[raw_data['device_id']](**raw_data)

			return obj_to_db

		update_data[0].map(lambda x:save_data_list.append(save_to_db(x,Base)) if x['device_id'] in exist_dev_id else None)
		# 如果存在多条相同device的数据，把所有更新叠加在一起,因为字典的健不能相同
		update_data[0].map(lambda x:this_dynamic.update({x['device_id']:x}) if x['device_id'] not in this_dynamic else this_dynamic[x['device_id']].update(x))
		# 整理update_data为dataframe，获取所有本次新上传的设备ID,新上传数据中过滤掉没有录入的设备数据，已录入的device数据过滤掉本次没上传数据的device，减少后续的计算和存储的量
		update_data=update_data[0].tolist()
		update_data=pd.DataFrame(update_data)
		update_data=update_data.loc[update_data['device_id'].isin(exist_dev_id)]
		update_dev_id=update_data['device_id'].tolist()
		device=device.loc[device['device_id'].isin(update_dev_id)]
		# 第二阶段，用新上传的数据更新device的dynamic字段，并且计算围栏，超速，健康报警等
		#1、更新基本数据，lng,lat,rssi,battray,speed,blood_pres,heart_rate,step_count,serv_receive,track_type
		def handle_dynamic(dev,new_data):
			# 需要更新的dev数据1
			owner=dev['owner']
			start=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
			old_data=eval(dev['dynamic'])
			# print(new_data['status'])
			all_alarm_status=old_data.get('alarm',{})
			if all_alarm_status==[]:
				all_alarm_status={}
			dynamic_status=eval(new_data['status'])
			# new_data['status']=eval(new_data['status'])


			if new_data['track_type']!='0':
				loca_chose={'satelite':[new_data['lng'],new_data['lat']],'wifi':new_data['wifi'],'lbs':new_data['lbs']}
				new_data['location']=str({new_data['track_type']:loca_chose[new_data['track_type']]})
			else:
				if old_data['track_type']!='0':
					loca_chose={'satelite':[old_data['lng'],old_data['lat']],'wifi':old_data['wifi'],'lbs':old_data['lbs']}
					new_data['location']==str({old_data['track_type']:loca_chose[old_data['track_type']]})
			
			# wifi和lbs的精度比较粗糙，计算围栏的话，会比较容易误报，如果客户有需求，可以考虑在前端做，即位置推送前端之后再计算是否有进出围栏2020.9.01吕康宁
			if new_data['lng']!='0' and dev['efence']!='0' and new_data['lng']!=old_data['lng'] and new_data['track_type']!='0':
				# 围栏报警计算

				dev['efence']=efence_compute(new_data,dev['efence'],owner)
			if new_data['lng']!='0' and dev['polyline']!='0' and new_data['lng']!=old_data['lng'] and new_data['track_type']!='0' and new_data['speed']!='0':
				# 线路偏移计算
				# print('cacu_poly==>',dev['polyline'])

				dev['polyline']=polyline_compute(new_data,polyline,dev['polyline'],owner)

			# 静止时长，速度为零的情况，都进行静止时长累加，用stop_time字段存储。2021.4.14吕康宁
			stop_time=0
			idling_time=0
			# stop_alarm字段计算存储速度为零，ACC开，的停车不熄火时长。2021.4.14吕康宁
			stop_alarm=old_data.get('stop_alarm','0')
			if stop_alarm=='0':
				stop_alarm={'idling_time':0}
			if new_data['speed']=='0' and old_data['serv_receive']!='0':
				this_receive_time=int(time.mktime(time.strptime(new_data['serv_receive'], "%Y-%m-%d %H:%M:%S")))
				last_receive_time=int(time.mktime(time.strptime(old_data['serv_receive'], "%Y-%m-%d %H:%M:%S")))
				stop_time=int(old_data['stop_time'])+(this_receive_time-last_receive_time)
				 # 统计停车不熄火时长，无速度，且ACC开，累计，否则清零
				if 'idling' in new_data['status']:
					idling_time=stop_alarm['idling_time']+(this_receive_time-last_receive_time)

			stop_alarm['idling_time']=idling_time
			if dev['param_set']!='0':
				stop_rate=eval(dev['param_set']).get('stop_rate')
				# 计算停留超时报警
				if stop_rate!=None:
					stop_rate=int(stop_rate)*60
					stop_alarm=stop_alarm_compute(new_data,stop_alarm,stop_rate,owner)

			old_data['stop_time']=stop_time
			# print('stop_alarm====>',stop_alarm)
			old_data['stop_alarm']=stop_alarm

			# 超速报警
			
			speed_alarm=old_data.get('speed_alarm','0')
			if speed_alarm=='0':
				speed_alarm={'alarm_status':'0'}
			if dev['param_set']!='0':
				speed_limit=eval(dev['param_set']).get('speed_alarm')
				if speed_limit!=None:
					speed_limit=float(speed_limit)
					speed_alarm=speed_alarm_compute(new_data,speed_limit,speed_alarm,owner)

			old_data['speed_alarm']=speed_alarm

			dev['status']=[]
			# 需要更新的dev数据3
			dev['status']=str(dev['status'])

			# 不在标准存库字段的数据存放在othermesg
			
			othermesg=eval(new_data.pop('othermesg'))

			# 其他状态报警，断电，sos，震动等
			alarm_mesg=othermesg.get('alarm',[])
			if new_data['sos_alarm']!='0':
				alarm_mesg.append('sos_alarm')
			# if len(alarm_mesg)!=0:
			all_alarm_status=other_alarm(new_data,alarm_mesg,dev['owner'],all_alarm_status)

			old_data['alarm']=all_alarm_status

			# analysis过来的数据的status字典存储有在线离线状态，dev里面改为dynamic的status字段存储，这样历史数据里面也可以拿到这个状态数据，方便计算在线离线率2020.9.03吕康宁

			new_data['on_off_line']=othermesg.get('on_off_line','on_line')
			new_data['receive_port']=othermesg.get('receive_port','0')
			dev['iccid']=othermesg.get('iccid',dev['iccid'])

			gps_num=othermesg.get('gps_num','0')
			bds_num=othermesg.get('bds_num','0')
			old_data['rssi_status']={'gps':gps_num,'bds':bds_num,'gsm':new_data['rssi']}

			send_time=new_data['serv_receive']
			# 完成命令的闭环，前端下发之后-django建立一张命令记录数据，此处update这条命令执行的结果，如果是device_set的命令，同步更改param_set里面的数据
			
			comd_log=mesg_redis.rpop(new_data['device_id']+'command_log')
			# print('comd_log====>',comd_log)
			if comd_log!=None:
				comd_log=eval(comd_log)
				send_time=comd_log['send_time']

		
				# 如果是对硬件进行的参数设置的反馈，结果是成功的话，对参数进行更改
				if comd_log['command_style']=='device_set' and comd_log['command_result']=='success':
					set_data='0'
					new_set={}
					new_set[comd_log['command_name']]=comd_log['command_value']

					if dev['param_set']!='0':
						if isinstance(eval(dev['param_set']),dict)==True:
							set_data=eval(dev['param_set'])
							set_data.update(new_set)
						else:
							set_data=new_set
					else:
						set_data=new_set

					if set_data!='0':
						# 需要更新的dev数据5
						dev['param_set']=str(set_data)

				comd_log['command_value']=str(comd_log['command_value'])
				comd_up_list.append(comd_log)


			for x in ['voice','picture','text']:
				if othermesg.get(x) !='0' and othermesg.get(x) !=None:
					# mesg_dict={'tk_mesg':'voice','img_data':'img','text_data':'text'}
					sender=dev['owner']
					recve=new_data['device_id']

					if othermesg.get('sender')!=None:
						sender=new_data['device_id']
						recve=dev['owner']

					media_mesg=othermesg[x]
					save_data={'send_time':send_time,'sender':sender,'receiver':recve,'location':new_data['location'],'content':media_mesg,'content_kind':x,'owner':dev['owner']}
					# 多媒体为固定的一张表格，不需要动态取标明，直接通过sqlachemy获取该表对象，打包存储的每条数据，加入存储列表，最后和设备信息一起批量存储。2020.0831吕康宁
					save_data_list.append(Multi_Media(**(save_data)))
					# 数据提交到redis
					# msg_to_db={'table_name':'car_app_multi_media','handle_kind':'add','content':tk_data}
					# mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))

			# 不为零的数据更新（后续在analysis里面把数据分为0和空，0是一个值，应该更新，空代表没有上传新数据，不用更新）
			for v in new_data:
				if new_data[v]!='0' and new_data!=0:
					old_data[v]=new_data[v]

			# 只要有经纬度，速度应该跟随变化，这样速度才能归零
			if new_data['lng']!='0':
				old_data['speed']=new_data['speed']

			# print('old_data====>',old_data)
			# 需要更新的dev数据6

			dev['dynamic']=str(old_data)
			dev=dev.to_dict()
			dev_up_list.append(dev)
			# print('dev====>',type(dev),dev)
			
			
			end=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
			

		# 统一用一个函数handle_dynamic来处理设备的新信息（绕回到老路子）2020.9.01吕康宁
		
		# print(device['device_id'])
		# print('this_dynamic====>',this_dynamic)
		dev_up_list=[]
		device=device.apply(lambda x:handle_dynamic(x,this_dynamic[x['device_id']]),axis=1)
		
		# dev_up_list=json.loads(device.to_json(orient="records",force_ascii=False))
		# print('dev_up_list====》',dev_up_list)

		# 获取报警数据，加入savedata列表,以及updata报警信息
		alarm_data=mesg_redis.lrange('car_alarm_to_db',0,-1)
		# print('alarm_data------',len(alarm_data),alarm_data)
		# 销毁已经获取的原始数据
		mesg_redis.ltrim('car_alarm_to_db',len(alarm_data),-1)
		if len(alarm_data)!=0:
			alarm_update_data={}
			alarm_data=pd.DataFrame(alarm_data)
			alarm_data[0]=alarm_data[0].map(lambda x:eval(x))
			alarm_data[0].map(lambda x:save_data_list.append(Alarm(**x['content'])) if x['handle_kind']=='add' else None)
			alarm_data[0].map(lambda x:x['content'].update({'id':alarm_unhandle[x['content']['name']]['id']}) if x['content']['name'] in alarm_unhandle else None)
			alarm_data[0].map(lambda x:alarm_up_list.append(x['content']) if x['handle_kind']=='update' else None)
			# print('have_alarm_data')
		# print('comd_up_list====>',comd_up_list)
		up_dict={'Device':dev_up_list,'Alarm':alarm_up_list,'Command_Log':comd_up_list}
		for table_name in up_dict:
			if len(up_dict[table_name])!=0:
				if table_name=='Command_Log':
					Base = automap_base()
					engine = create_engine('sqlite:///car.db')
					Base.prepare(engine, reflect=True)
				if table_name=='Alarm':
					print('alarm_up_list==>',alarm_up_list)
				session.bulk_update_mappings(eval(table_name),up_dict[table_name])
		session.add_all(save_data_list)
		session.commit()
		# print('all_update_data-----------------',up_dict)
		# print('start=',start,'end=',end)